package database

import ast.expr.SqlIdentifierExpr
import ast.statement.upsert.SqlUpsert
import dsl.OrderBy
import dsl.Query
import dsl.QueryTableColumn
import dsl.TableSchema
import query.Page
import query.ddl.CreateIndex
import query.ddl.CreateTable
import query.ddl.DropIndex
import query.ddl.DropTable
import query.delete.Delete
import query.insert.Insert
import query.insert.NativeInsert
import query.select.NativeSelect
import query.select.Select
import query.select.ValuesSelect
import query.select.WithSelect
import query.truncate.Truncate
import query.update.Update
import util.getOutPutVisitor
import util.toSqlString
import visitor.checkOLAP
import visitor.getExpr
import visitor.getQueryExpr
import java.sql.SQLException
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.declaredMemberProperties

/**
 * 数据库交互抽象类
 */
abstract class DataBaseImpl : DataBase {
    /**
     * 创建一个select查询
     * 例如：db.select("c1", "c2")
     * @param columns Array<out String> 字段名列表
     * @return Select 查询dsl
     */
    open fun select(vararg columns: String): Select {
        val select = Select(db, getConnection(), isTransaction)
        select.select(*columns)
        return select
    }

    /**
     * 创建一个select查询
     * 例如：db select count()
     * @param query Query 查询表达式
     * @return Select 查询dsl
     */
    open infix fun select(query: Query): Select {
        val select = Select(db, getConnection(), isTransaction)
        select.invoke(query)
        return select
    }

    /**
     * 创建一个select查询
     * 例如：db select listOf(count(), sum("c1"))
     * @param query List<Query> 查询表达式列表
     * @return Select 查询dsl
     */
    open infix fun select(query: List<Query>): Select {
        val select = Select(db, getConnection(), isTransaction)
        select.invoke(query)
        return select
    }

    /**
     * 创建一个select查询
     * 例如：db.select(count(), sum("c1"))
     * @param query Array<out Query> 查询表达式列表
     * @return Select 查询dsl
     */
    open fun select(vararg query: Query): Select {
        val select = Select(db, getConnection(), isTransaction)
        select.select(*query)
        return select
    }

    /**
     * 创建一个select查询
     * 例如：db.select()
     * @return Select 查询dsl
     */
    open fun select(): Select {
        return Select(db, getConnection(), isTransaction)
    }

    /**
     * 创建一个select查询
     * @param table String 表名
     * @return Select
     */
    open fun from(table: String): Select {
        return Select(db, getConnection(), isTransaction).from(table)
    }

    /**
     * 创建一个select查询
     * @param table TableSchema 实体类伴生对象名
     * @return Select
     */
    open fun from(table: TableSchema): Select {
        return Select(db, getConnection(), isTransaction).from(table)
    }

    /**
     * 创建一个原生sql查询
     * 例如：db.nativeSelect("select * from t1 where c1 = ?", 1)
     * @param sql String 查询sql语句
     * @param arg Array<out Any> 查询参数列表（可省略），查询语句中的?会被arg中的参数依次替换，合法的类型有Number、String、Date、List、Boolean以及null和Query表达式类型
     * @return NativeSelect 原生sql查询
     */
    open fun nativeSelect(sql: String, vararg arg: Any): NativeSelect {
        val argList = arg.map {
            if (it is Query) {
                val visitor = getOutPutVisitor(db)
                visitor.visitSqlExpr(getQueryExpr(it, db).expr)
                visitor.sql()
            } else {
                getExpr(it).toString()
            }
        }
        var nativeSql = sql
        if (sql.contains("?")) {
            argList.forEach {
                nativeSql = nativeSql.replaceFirst("?", it)
            }
        }
        return NativeSelect(db, nativeSql, getConnection(), isTransaction)
    }

    /**
     * 创建一个with查询
     * @return WithSelect with查询dsl
     */
    open fun with(): WithSelect {
        return WithSelect(db, getConnection(), isTransaction)
    }

    /**
     * 创建一个values查询
     * @param value Array<out List<Any>> value列表
     * @return ValuesSelect values查询dsl
     */
    open fun values(vararg value: List<Any>): ValuesSelect {
        val values = ValuesSelect(db, getConnection(), isTransaction)
        value.forEach {
            values.addRow(it)
        }
        return values
    }

    /**
     * 按主键查询一行数据
     * 例如：db.find<Table>(1)
     * @param primaryKey Any 表主键的值
     * @return T? 查询返回的实体
     */
    inline fun <reified T> find(primaryKey: Any): T? {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")

        val tableName = (companion as TableSchema).tableName

        val companionClass = companion::class
        val pkCols = companionClass.declaredMemberProperties
            .map { it.getter.call(companion) }
            .filterIsInstance<QueryTableColumn>()
            .filter { it.primaryKey }

        if (pkCols.isEmpty()) {
            throw Exception("实体类的伴生对象中没有设置主键字段")
        }

        if (pkCols.size > 1) {
            throw SQLException("主键字段数量和传入的参数不匹配")
        }

        val pkCol = pkCols[0]

        return select().from(tableName).where(pkCol eq primaryKey).find()
    }

    /**
     * 按主键查询一行数据（用于联合主键的表）
     * 例如：db.find<Table>(listOf("c1" to 1, "c2" to 2))
     * @param primaryKeys List<Pair<String, Any>> 表主键字段名和对应值的列表
     * @return T? 查询返回的实体
     */
    inline fun <reified T> find(primaryKeys: List<Pair<String, Any>>): T? {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")

        val tableName = (companion as TableSchema).tableName

        val companionClass = companion::class
        val pkCols = companionClass.declaredMemberProperties
            .map { it.name to it.getter.call(companion) }
            .filter { it.second is QueryTableColumn }
            .map { it.first to it.second as QueryTableColumn }
            .filter { it.second.primaryKey }
            .toMap()

        if (pkCols.isEmpty()) {
            throw Exception("实体类的伴生对象中没有设置主键字段")
        }

        if (pkCols.size != primaryKeys.size) {
            throw SQLException("主键字段数量和传入的参数不匹配")
        }

        val select = select().from(tableName)
        primaryKeys.forEach {
            if (pkCols.containsKey(it.first)) {
                select.where(pkCols[it.first]!! eq it.second)
            }
        }

        return select.find()
    }

    /**
     * 按主键查询一行数据（用于联合主键的表）
     * 例如：db.find<Table>(mapOf("c1" to 1, "c2" to 2))
     * @param primaryKeys Map<String, Any> 表主键字段名和对应值的列表
     * @return T? 查询返回的实体
     */
    inline fun <reified T> find(primaryKeys: Map<String, Any>): T? {
        return find(primaryKeys.map { it.key to it.value })
    }

    /**
     * 查询整表结果
     * 例如：db.queryAll<Table>()
     * @return List<T> 查询返回的列表
     */
    inline fun <reified T> queryAll(): List<T> {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val tableName = (companion as TableSchema).tableName

        val select = select().from(tableName)
        return select.query()
    }

    /**
     * 分页查询
     * 例如：db.page<Table>(10, 1, false)
     * @param pageSize Int 一页的条数
     * @param pageNumber Int 页数
     * @param needCount Boolean 是否需要查询COUNT(*)，可省略，默认为true
     * @return Page<T> 返回的一页结果集
     */
    inline fun <reified T> page(pageSize: Int, pageNumber: Int, needCount: Boolean = true): Page<T> {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val tableName = (companion as TableSchema).tableName

        val select = select().from(tableName)
        return select.page(pageSize, pageNumber, needCount)
    }

    /**
     * 获取总数
     * 例如：db.fetchCount<Table>()
     * @return Long 总条数
     */
    inline fun <reified T> fetchCount(): Long {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val tableName = (companion as TableSchema).tableName

        val select = select().from(tableName)
        return select.fetchCount()
    }

    /**
     * 获取首页
     * 例如：db.firstPage<Table>(Table.c1.asc(), 10)
     * @param sort OrderBy 字段的排序规则
     * @param pageSize Int 一页的条数
     * @return List<T> 查询返回的列表
     */
    inline fun <reified T> firstPage(sort: OrderBy, pageSize: Int): List<T> {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val tableName = (companion as TableSchema).tableName

        val select = select().from(tableName)
        return select.firstPage(sort, pageSize)
    }

    /**
     * 获取尾页
     * 例如：db.lastPage<Table>(Table.c1.asc(), 10)
     * @param sort OrderBy 字段的排序规则
     * @param pageSize Int 一页的条数
     * @return List<T> 查询返回的列表
     */
    inline fun <reified T> lastPage(sort: OrderBy, pageSize: Int): List<T> {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val tableName = (companion as TableSchema).tableName

        val select = select().from(tableName)
        return select.lastPage(sort, pageSize)
    }

    /**
     * 获取上一页
     * 例如：db.previousPage<Table>(Table.c1.asc(), 11, 10)
     * @param sort OrderBy 字段的排序规则
     * @param value Any 当前页第一条数据中，排序字段的值
     * @param pageSize Int 一页的条数
     * @return List<T> 查询返回的列表
     */
    inline fun <reified T> previousPage(sort: OrderBy, value: Any, pageSize: Int): List<T> {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val tableName = (companion as TableSchema).tableName

        val select = select().from(tableName)
        return select.previousPage(sort, value, pageSize)
    }

    /**
     * 获取下一页
     * 例如：db.previousPage<Table>(Table.c1.asc(), 10, 10)
     * @param sort OrderBy 字段的排序规则
     * @param value Any 当前页最后一条数据中，排序字段的值
     * @param pageSize Int 一页的条数
     * @return List<T> 查询返回的列表
     */
    inline fun <reified T> nextPage(sort: OrderBy, value: Any, pageSize: Int): List<T> {
        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val tableName = (companion as TableSchema).tableName

        val select = select().from(tableName)
        return select.nextPage(sort, value, pageSize)
    }

    /**
     * 创建一个更新操作
     * 例如：db update "t1"
     * @param table String 表名
     * @return Update 更新dsl
     */
    infix fun update(table: String): Update {
        checkOLAP(this.db)

        val update = Update(db, getConnection(), isTransaction)
        update.update(table)
        return update
    }

    /**
     * 创建一个更新操作
     * 例如：db update Table
     * @param table TableSchema 实体类伴生对象名
     * @return Update 更新dsl
     */
    infix fun update(table: TableSchema): Update {
        checkOLAP(this.db)

        val update = Update(db, getConnection(), isTransaction)
        update.update(table)
        return update
    }

    /**
     * 按主键更新其他字段的值（空字段会被忽略）
     * 例如：db update entity
     * @param entity Any 实体对象
     * @return Int 受影响的行数
     */
    infix fun update(entity: Any): Int {
        checkOLAP(this.db)

        val update = Update(db, getConnection(), isTransaction)
        update.update(entity)
        return update.exec()
    }

    /**
     * 创建一个无实体类的插入操作
     * 例如：db insert "t1"
     * @param table String 表名
     * @return NativeInsert 插入dsl
     */
    infix fun insert(table: String): NativeInsert {
        checkOLAP(this.db)

        val insert = NativeInsert(db, getConnection(), isTransaction)
        insert.into(table)
        return insert
    }

    /**
     * 创建一个实体类的插入操作
     * 例如：db insert Table
     * @param table TableSchema 实体类伴生对象名
     * @return Insert 插入dsl
     */
    infix fun insert(table: TableSchema): Insert {
        checkOLAP(this.db)

        val insert = Insert(db, getConnection(), isTransaction)
        insert.into(table)
        return insert
    }

    /**
     * 插入实体数据（自增主键会被忽略）
     * 例如：db insert entity
     * @param entity Any 实体对象
     * @return Int 受影响的行数
     */
    infix fun insert(entity: Any): Int {
        checkOLAP(this.db)

        val insert = Insert(db, getConnection(), isTransaction)
        insert.insert(entity)
        return insert.exec()
    }

    /**
     * 批量插入实体数据（自增主键会被忽略）
     * 例如：db insert listOf(entity1, entity2)
     * @param entity List<Any> 实体对象列表
     * @return Int 受影响的行数
     */
    infix fun insert(entity: List<Any>): Int {
        checkOLAP(this.db)

        val insert = Insert(db, getConnection(), isTransaction)
        insert.insert(entity)
        return insert.exec()
    }

    /**
     * 按实体数据中的主键字段决定插入或更新
     * 例如：db save entity
     * @param entity Any 实体对象
     * @return Int 受影响的行数
     */
    infix fun save(entity: Any): Int {
        checkOLAP(this.db)
        val upsert = toUpsert(entity)
        val sql = toSqlString(upsert, db)
        return exec(getConnection(), sql)
    }

    /**
     * 将实体类实例转换为Upsert对象
     * @param entity Any 实体对象
     * @return Upsert 实体数据转换后Upsert对象
     */
    fun toUpsert(entity: Any): SqlUpsert {
        val clazz = entity::class
        val companion = clazz.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val table = companion as TableSchema
        val tableName = table.tableName
        val properties = table::class.declaredMemberProperties.map { it.name to it.getter.call(table) }
            .filter { it.second is QueryTableColumn }
            .map { it.first to it.second as QueryTableColumn }
        val primaryColumns = properties.filter { it.second.primaryKey }.map { SqlIdentifierExpr(it.second.column) }
        if (primaryColumns.isEmpty()) {
            throw SQLException("实体类的伴生对象中没有设置主键字段")
        }
        val columns = properties.map { SqlIdentifierExpr(it.second.column) }
        val updateColumns = properties.filter { !it.second.primaryKey }.map { SqlIdentifierExpr(it.second.column) }
        val entityProperties = clazz.declaredMemberProperties.map { it.name to it.getter.call(entity) }.toMap()
        val value = properties.map { getExpr(entityProperties[it.first]) }
        val upsert = SqlUpsert(SqlIdentifierExpr(tableName))
        upsert.columns.addAll(columns)
        upsert.primaryColumns.addAll(primaryColumns)
        upsert.updateColumns.addAll(updateColumns)
        upsert.value.addAll(value)
        return upsert
    }

    /**
     * 创建一个删除操作
     * 例如：db delete "t1"
     * @param table String 表名
     * @return Insert 删除dsl
     */
    infix fun delete(table: String): Delete {
        checkOLAP(this.db)

        val delete = Delete(db, getConnection(), isTransaction)
        delete.from(table)
        return delete
    }

    /**
     * 创建一个删除操作
     * 例如：db delete Table
     * @param table TableSchema 实体类伴生对象名
     * @return Insert 删除dsl
     */
    infix fun delete(table: TableSchema): Delete {
        checkOLAP(this.db)

        val delete = Delete(db, getConnection(), isTransaction)
        delete.from(table)
        return delete
    }

    /**
     * 按传入实体类的主键字段删除数据
     * 例如：db delete entity
     * @param entity Any 实体对象
     * @return Int 受影响的行数
     */
    infix fun delete(entity: Any): Int {
        checkOLAP(this.db)

        val delete = Delete(db, getConnection(), isTransaction)
        delete.delete(entity)
        return delete.exec()
    }

    /**
     * 按主键删除数据
     * 例如：db.delete<Table>(1)
     * @param primaryKey Any 主键字段的值
     * @return Int 受影响的行数
     */
    @JvmName("deleteById")
    inline infix fun <reified T> delete(primaryKey: Any): Int {
        checkOLAP(this.db)

        val delete = Delete(db, getConnection(), isTransaction)
        delete.delete<T>(primaryKey)
        return delete.exec()
    }

    /**
     * 按主键删除数据（用于联合主键的表）
     * 例如：db.delete<Table>(listOf("c1" to 1, "c2" to 2))
     * @param primaryKeys List<Pair<String, Any>> 表主键字段名和对应值的列表
     * @return Int 受影响的行数
     */
    inline fun <reified T> delete(primaryKeys: List<Pair<String, Any>>): Int {
        checkOLAP(this.db)

        val delete = Delete(db, getConnection(), isTransaction)
        delete.delete<T>(primaryKeys)
        return delete.exec()
    }

    /**
     * 按主键删除数据（用于联合主键的表）
     * 例如：db.delete<Table>(mapOf("c1" to 1, "c2" to 2))
     * @param primaryKeys Map<String, Any> 表主键字段名和对应值的列表
     * @return Int 受影响的行数
     */
    inline fun <reified T> delete(primaryKeys: Map<String, Any>): Int {
        checkOLAP(this.db)

        val delete = Delete(db, getConnection(), isTransaction)
        delete.delete<T>(primaryKeys)
        return delete.exec()
    }

    /**
     * 创建一个清空操作
     * 例如：db truncate "t1"
     * @param table String 表名
     * @return Truncate 清空dsl
     */
    infix fun truncate(table: String): Truncate {
        checkOLAP(this.db)

        val truncate = Truncate(db, getConnection(), isTransaction)
        truncate.truncate(table)
        return truncate
    }

    /**
     * 创建一个清空操作
     * 例如：db truncate Table
     * @param table TableSchema 实体类伴生对象名
     * @return Truncate 清空dsl
     */
    infix fun truncate(table: TableSchema): Truncate {
        checkOLAP(this.db)

        val truncate = Truncate(db, getConnection(), isTransaction)
        truncate.truncate(table)
        return truncate
    }

    /**
     * 创建一个删表操作
     * 例如：db dropTable "t1"
     * @param table String 表名
     * @return DropTable 删表dsl
     */
    infix fun dropTable(table: String): DropTable {
        val dropTable = DropTable(db, getConnection(), isTransaction)
        dropTable.drop(table)
        return dropTable
    }

    /**
     * 创建一个删索引操作
     * 例如：db dropIndex "idx1"
     * @param indexName String 表名
     * @return DropIndex 删索引dsl
     */
    infix fun dropIndex(indexName: String): DropIndex {
        val dropIndex = DropIndex(db, getConnection(), isTransaction)
        dropIndex.drop(indexName)
        return dropIndex
    }

    /**
     * 创建一个建表操作
     * 例如：db createTable "t1"
     * @param table String 表名
     * @return CreateTable 建表dsl
     */
    infix fun createTable(table: String): CreateTable {
        val createTable = CreateTable(db, getConnection(), isTransaction)
        createTable.create(table)
        return createTable
    }

    /**
     * 创建一个建索引操作
     * 例如：db createIndex "t1"
     * @param indexName String 表名
     * @return CreateIndex 建索引dsl
     */
    infix fun createIndex(indexName: String): CreateIndex {
        val createIndex = CreateIndex(db, getConnection(), isTransaction)
        createIndex.create(indexName)
        return createIndex
    }

    /**
     * 创建一个建唯一索引的操作
     * 例如：db createUniqueIndex "t1"
     * @param indexName String 表名
     * @return CreateIndex 建索引dsl
     */
    infix fun createUniqueIndex(indexName: String): CreateIndex {
        val createIndex = CreateIndex(db, getConnection(), isTransaction)
        createIndex.createUnique(indexName)
        return createIndex
    }
}